﻿package idv.cjcat.shintaz.compiler.scanner {
	import flash.utils.Dictionary;
	import idv.cjcat.shintaz.errors.ScannerError;
	
	public class Scanner implements IScanner {
		
		private var _code:String;
		public function get code():String { return _code; }
		
		private var _charIndex:int;
		private var _lineIndex:int;
		public function get lineNumber():int { return _lineIndex; }
		
		private var _keywords:Dictionary;
		private var _delimiters:Dictionary;
		
		public function Scanner(code:String) {
			_code = code;
			_charIndex = 0;
			_lineIndex = 0;
			
			_keywords = TokenDictionary.getInstance().keywords;
			_delimiters = TokenDictionary.getInstance().delimiters;
		}
		
		public function rewind():void {
			_charIndex = 0;
			_lineIndex = 0;
		}
		
		public function getLineNumber():int { return _lineIndex + 1; }
		public function getLine():String { return _code.split("\n")[_lineIndex]; }
		
		private var _char:String;
		private var _token:Token;
		private var _value:String;
		
		public function getToken():Token {
			
			_char = _code.charAt(_charIndex);
			_token;
			_value= "";
			
			//------------------------------------------------------------------------------------------------
			
			//skip the white spaces
			while (Pattern.isWhiteSpace(_char)) {
				_char = _code.charAt(++_charIndex);
			}
			
			//no characters left
			if (!_char) return null;
			
			switch (_char.charCodeAt(0)) {
				case CharCode.U_LINE: case CharCode.MONEY: 
				case CharCode.LOWER_A: case CharCode.LOWER_B: case CharCode.LOWER_C: case CharCode.LOWER_D: case CharCode.LOWER_E: case CharCode.LOWER_F: case CharCode.LOWER_G: case CharCode.LOWER_H: case CharCode.LOWER_I: case CharCode.LOWER_J: case CharCode.LOWER_K: case CharCode.LOWER_L: case CharCode.LOWER_M: case CharCode.LOWER_N: case CharCode.LOWER_O: case CharCode.LOWER_P: case CharCode.LOWER_Q: case CharCode.LOWER_R: case CharCode.LOWER_S: case CharCode.LOWER_T: case CharCode.LOWER_U: case CharCode.LOWER_V: case CharCode.LOWER_W: case CharCode.LOWER_X: case CharCode.LOWER_Y: case CharCode.LOWER_Z: case CharCode.UPPER_A: case CharCode.UPPER_B: case CharCode.UPPER_C: case CharCode.UPPER_D: case CharCode.UPPER_E: case CharCode.UPPER_F: case CharCode.UPPER_G: case CharCode.UPPER_H: case CharCode.UPPER_I: case CharCode.UPPER_J: case CharCode.UPPER_K: case CharCode.UPPER_L: case CharCode.UPPER_M: case CharCode.UPPER_N: case CharCode.UPPER_O: case CharCode.UPPER_P: case CharCode.UPPER_Q: case CharCode.UPPER_R: case CharCode.UPPER_S: case CharCode.UPPER_T: case CharCode.UPPER_U: case CharCode.UPPER_V: case CharCode.UPPER_W: case CharCode.UPPER_X: case CharCode.UPPER_Y: case CharCode.UPPER_Z:
					return alphabet();
					break;
				case CharCode.NUM_0: case CharCode.NUM_1: case CharCode.NUM_2: case CharCode.NUM_3: case CharCode.NUM_4: case CharCode.NUM_5: case CharCode.NUM_6: case CharCode.NUM_7: case CharCode.NUM_8: case CharCode.NUM_9:
				case CharCode.DOT:
					return number();
					break;
				case CharCode.D_QUOTE:
					return stringLiteral();
					break;
				case CharCode.PLUS:
					return plus();
					break;
				case CharCode.MINUS:
					return minus();
					break;
				case CharCode.SLASH:
					return slash();
					break;
				case CharCode.ASTER:
					return asterisk();
					break;
				case CharCode.PIPE:
					return pipe();
					break;
				case CharCode.AMPER:
					return ampersand();
					break;
				case CharCode.TILDA:
					return tilda();
					break;
				case CharCode.HEAD:
					return head();
					break;
				case CharCode.EXCLA:
					return exclamation();
					break;
				case CharCode.EQ:
					return equal();
					break;
				case CharCode.LT:
					return lessThan();
					break;
				case CharCode.GT:
					return greaterThan();
					break;
			}
			
			//delimiters
			_token = _delimiters[_char];
			if (_token) {
				_charIndex++;
				return _token; //delimiter
			}
			
			//scanned token unrecognized
			throw new ScannerError("Unrecognized token: \"" + _value + "\"", this);
		}
		
		/**
		 * keywords
		 * identifiers
		 * @return
		 */
		private function alphabet():Token {
			//keywords & identifiers
			_value = _char;
			while ((_char = _code.charAt(++_charIndex)) && Pattern.isIdentifierBody(_char)) {
				_value += _char;
			}
			
			_token = _keywords[_value];
			if (_token) {
				return _token; //keyword
			}
			
			return new Token(TokenType.ID, _value); //identifier
		}
		
		private function number():Token {
			_value = _char;
			if (_char == "0") {
				if ((_char = _code.charAt(++_charIndex)) && ((_char == "x") || (_char == "X"))) {
					_value += _char;
					while ((_char = _code.charAt(++_charIndex)) && Pattern.isHex(_char)) {
						_value += _char;
					}
				} else if (Pattern.isNumber(_char)) {
					_value += _char;
					while ((_char = _code.charAt(++_charIndex)) && Pattern.isNumber(_char)) {
						_value += _char;
					}
				}
			} else {
				while ((_char = _code.charAt(++_charIndex)) && Pattern.isNumber(_char)) {
					_value += _char;
				}
			}
			
			if (_char == ".") {
				_value += _char;
				if ((_char = _code.charAt(++_charIndex)) != null) {
					if (Pattern.isNumber(_char)) {
						_value += _char;
					}
				}
				
				while ((_char = _code.charAt(++_charIndex)) && Pattern.isNumber(_char)) {
					_value += _char;
				}
			}
			
			if (_value == ".") return Token.DOT; // .
			if (_value.indexOf(".") >= 0) return new Token(TokenType.NUMBER, parseFloat(_value)); //float
			else return new Token(TokenType.NUMBER, parseInt(_value)); //integer
		}
		
		private function stringLiteral():Token {
			_value = "";
			while ((_char = _code.charAt(++_charIndex)) && (_char != "\"")) {
				if (_char == "\\") {
					_char = _code.charAt(++_charIndex);
					if (_char == "n") {
						_value += "\n";
						continue;
					} else if (_char == "\\") {
						_value += "\\";
						continue;
					} else if (_char == "t") {
						_value += "\t";
						continue;
					}
				}
				_value += _char;
			}
			if (_char != "\"") {
				throw new ScannerError("String literal not closed before end of code.", this);
			}
			
			_charIndex++;
			return new Token(TokenType.STRING, _value); //string
		}
		
		/**
		 * ++
		 * +=
		 * +
		 */
		private function plus():Token {
			if ((_char = _code.charAt(++_charIndex)) != null) {
				switch (_char.charCodeAt(0)) {
					case CharCode.PLUS:
						_charIndex++;
						return Token.INCRE; // ++
						break;
					case CharCode.EQ:
						_charIndex++;
						return Token.ASSIGN_PLUS; // +=
					default:
						return Token.PLUS; // +
						break;
				}
			}
			return null;
		}
		
		/**
		 * --
		 * -=
		 * -
		 */
		private function minus():Token {
			if ((_char = _code.charAt(++_charIndex)) != null) {
				switch (_char.charCodeAt(0)) {
					case CharCode.MINUS:
						_charIndex++;
						return Token.DECRE; // --
						break;
					case CharCode.EQ:
						_charIndex++;
						return Token.ASSIGN_MINUS; // -=
					default:
						return Token.MINUS; // +
						break;
				}
			}
			return null;
		}
		
		private function slash():Token {
			//comments & /=
			if ((_char = _code.charAt(++_charIndex)) != null) {
				
				if (_char == "=") {
					_charIndex++;
					return Token.ASSIGN_DIV; // /=
				}
				
				/**
				 * single-line comment
				 */
				if (_char == "/") {
					while ((_char = _code.charAt(++_charIndex)) && (_char != "\n")) {
						//do nothing, consumming single-line commen
					}
					_charIndex++;
					return getToken();
				}
				
				/**
				 * multi-line comment
				 */
				if (_char == "*") {
					while (_char = _code.charAt(++_charIndex)) {
						if (_char == "*") {
							if ((_char = _code.charAt(++_charIndex)) && (_char == "/")) {
								//end of multi-line comment
								break;
							}
						}
					}
					_charIndex++;
					return getToken();
				}
				
				return Token.DIV; // /
			}
			return null;
		}
		
		/**
		 * *=
		 * *
		 */
		private function asterisk():Token {
			if ((_char = _code.charAt(++_charIndex)) != null) {
				switch (_char.charCodeAt(0)) {
					case CharCode.EQ:
						_charIndex++;
						return Token.ASSIGN_MUL; // *=
						break;
					default:
						return Token.MUL; // *
						break;
				}
			}
			return null;
		}
		
		/**
		 * ||
		 * |=
		 * |
		 */
		private function pipe():Token {
			if ((_char = _code.charAt(++_charIndex)) != null) {
				switch (_char.charCodeAt(0)) {
					case CharCode.PIPE:
						_charIndex++;
						return Token.OR; // ||
						break;
					case CharCode.EQ:
						_charIndex++;
						return Token.ASSIGN_BIT_OR; // |=
					default:
						return Token.BIT_OR; // |
						break;
				}
			}
			return null;
		}
		
		/**
		 * &&
		 * &=
		 * &
		 */
		private function ampersand():Token {
			if ((_char = _code.charAt(++_charIndex)) != null) {
				switch (_char.charCodeAt(0)) {
					case CharCode.AMPER:
						_charIndex++;
						return Token.AND; // &&
						break;
					case CharCode.EQ:
						_charIndex++;
						return Token.ASSIGN_BIT_AND; // &=
					default:
						return Token.BIT_AND; // &
						break;
				}
			}
			return null;
		}
		
		/**
		 * ~=
		 * ~
		 */
		private function tilda():Token {
			if ((_char = _code.charAt(++_charIndex)) != null) {
				switch (_char.charCodeAt(0)) {
					case CharCode.EQ:
						_charIndex++;
						return Token.ASSIGN_BIT_NOT; // ~=
					default:
						return Token.BIT_NOT; // ~
						break;
				}
			}
			return null;
		}
		
		/**
		 * ^=
		 * ^
		 */
		private function head():Token {
			if ((_char = _code.charAt(++_charIndex)) != null) {
				switch (_char.charCodeAt(0)) {
					case CharCode.EQ:
						_charIndex++;
						return Token.ASSIGN_BIT_XOR; // ^=
					default:
						return Token.BIT_XOR; // ^
						break;
				}
			}
			return null;
		}
		
		/**
		 * !=
		 * !
		 */
		private function exclamation():Token {
			if ((_char = _code.charAt(++_charIndex)) != null) {
				switch (_char.charCodeAt(0)) {
					case CharCode.EQ:
						_charIndex++;
						return Token.NEQ; // !=
					default:
						return Token.NOT; // !
						break;
				}
			}
			return null;
		}
		
		/**
		 * ==
		 * =
		 */
		private function equal():Token {
			if ((_char = _code.charAt(++_charIndex)) != null) {
				if (_char == "=") {
					_charIndex++;
					return Token.EQ; // ==
				} else {
					return Token.ASSIGN; //
				}
			}
			return null;
		}
		
		/**
		 * <<<=
		 * <<<
		 * <<=
		 * <<
		 * <=
		 * <
		 */
		private function lessThan():Token {
			if ((_char = _code.charAt(++_charIndex)) != null) {
				switch (_char.charCodeAt(0)) {
					case CharCode.LT:
						if ((_char = _code.charAt(++_charIndex)) != null) {
							switch (_char.charCodeAt(0)) {
								case CharCode.LT:
									if ((_char = _code.charAt(++_charIndex)) != null) {
										switch (_char.charCodeAt(0)) {
											case CharCode.EQ:
												_charIndex++;
												return Token.ASSIGN_USHIFT_L; // <<<=
												break;
											default:
												return Token.USHIFT_L; // <<<
												break;
										}
									}
									break;
								case CharCode.EQ:
									_charIndex++;
									return Token.ASSIGN_SHIFT_L; // <<=
									break;
								default:
									return Token.SHIFT_L; // <<
									break;
							}
						}
						break;
					case CharCode.EQ:
						_charIndex++;
						return Token.LE; // <=
						break;
					default:
						return Token.LT; // <
						break;
				}
			}
			return null;
		}
		
		/**
		 * >>>=
		 * >>>
		 * >>=
		 * >>
		 * >=
		 * >
		 */
		private function greaterThan():Token {
			if ((_char = _code.charAt(++_charIndex)) != null) {
				switch (_char.charCodeAt(0)) {
					case CharCode.GT:
						if ((_char = _code.charAt(++_charIndex)) != null) {
							switch (_char.charCodeAt(0)) {
								case CharCode.GT:
									if ((_char = _code.charAt(++_charIndex)) != null) {
										switch (_char.charCodeAt(0)) {
											case CharCode.EQ:
												_charIndex++;
												return Token.ASSIGN_USHIFT_R; // >>>=
												break;
											default:
												return Token.USHIFT_R; // >>>
												break;
										}
									}
									break;
								case CharCode.EQ:
									_charIndex++;
									return Token.ASSIGN_SHIFT_R; // >>=
									break;
								default:
									return Token.SHIFT_R; // >>
									break;
							}
						}
						break;
					case CharCode.EQ:
						_charIndex++;
						return Token.GE; // >=
						break;
					default:
						return Token.GT; // >
						break;
				}
			}
			return null;
		}
	}
}